
/**
* 上海中赢金融信息服务有限公司
* Copyright (c) 2017, chinazyjr All Rights Reserved.
*/
package com.zhongying.jf.util.dislock;

import com.sys.midware.logger.Logger;
import com.sys.midware.logger.LoggerFactory;
import com.zhongying.jf.util.redis.RedisUtils;

import redis.clients.jedis.Jedis;

/**
 * <b>Description：</b> distributed lock implementation. <br/>
 * <b>ClassName：</b> JedisLock <br/>
 * <b>@author：</b> mobing <br/>
 * <b>@date：</b> 2017年7月24日 下午5:59:08 <br/>
 * <b>@version: </b> <br/>
 */
public class DLockUtil4Redis {

    private static final Logger logger = LoggerFactory.getLogger(DLockUtil4Redis.class);

    private static String DEFAULT_LOCK_KEY = "public_distribute_lock";

    // 锁超时，防止线程在入锁以后，无限的执行等待，当出现异常，则锁在1分钟后自动失效
    private static int DEFAULT_LOCK_EXPIRE_MSECS = 10 * 60 * 1000;

    // 锁等待，防止线程饥饿,当锁被其他对象获取，当前线程等待3秒，以便能获取到锁
    private static int DEFAULT_LOCK_WAIT_MSECS = 3 * 1000;

    /**
     * Lock key path.
     */
    private String lockKey;

    /**
     * Lock expiration in miliseconds.
     */
    private int expireMsecs; // 锁超时，防止线程在入锁以后，无限的执行等待

    /**
     * Acquire timeout in miliseconds.
     */
    private int timeoutMsecs; // 锁等待，防止线程饥饿

    private boolean locked;

    private Jedis getClient() {
        return RedisUtils.getClient();
    }

    public DLockUtil4Redis() {
        this.expireMsecs = DEFAULT_LOCK_EXPIRE_MSECS;
        this.lockKey = DEFAULT_LOCK_KEY;
        this.timeoutMsecs = DEFAULT_LOCK_WAIT_MSECS;
    }

    public DLockUtil4Redis(String lockKey, int timeoutMsecs) {
        this.expireMsecs = DEFAULT_LOCK_EXPIRE_MSECS;
        this.lockKey = lockKey;
        this.timeoutMsecs = timeoutMsecs;
    }

    public DLockUtil4Redis(String lockKey) {
        this.expireMsecs = DEFAULT_LOCK_EXPIRE_MSECS;
        this.lockKey = lockKey;
        this.timeoutMsecs = DEFAULT_LOCK_WAIT_MSECS;
    }

    public DLockUtil4Redis(String lockKey, int timeoutMsecs, int expireMsecs) {
        this.expireMsecs = expireMsecs;
        this.lockKey = lockKey;
        this.timeoutMsecs = timeoutMsecs;

    }

    /**
     * @return lock key
     */
    public String getLockKey() {
        return lockKey;
    }

    /**
     * Acquire lock.
     * 
     * @param jedis
     * @return true if lock is acquired, false acquire timeouted
     * @throws InterruptedException
     *             in case of thread interruption
     */
    public synchronized boolean acquire() throws InterruptedException {
    	try(Jedis jedis = getClient()){
    		logger.info("begin to acquire distributed lock.lockKey=" + lockKey);
            // 超时时间防止线程饥饿
            int timeout = timeoutMsecs;
            while (timeout >= 0) {
                long expires = System.currentTimeMillis() + expireMsecs + 1;
                String expiresStr = String.valueOf(expires); // 锁到期时间

                if (jedis.setnx(lockKey, expiresStr) == 1) {
                    logger.info("Get a successful distribution lock via jedis.setnx!+lock.lockKey="+lockKey);
                    // lock acquired
                    locked = true;
                    return true;
                } else {
                    logger.debug("The lock has been get by someone else! lockKey="+lockKey);
                }
                // 如果setnx出现异常或失效，则进行再次校验，防止出现死锁情况
                String currentValueStr = jedis.get(lockKey); // redis里的时间
                logger.info(lockKey+",currentValueStr="+currentValueStr+"------>"+System.currentTimeMillis());
                if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
                    // 判断是否为空，不为空的情况下，如果被其他线程设置了值，则第二个条件判断是过不去的
                    // lock is expired
                    logger.info("The two check prevents deadlock via jedis.getSet! lockKey="+lockKey);
                    String oldValueStr = jedis.getSet(lockKey, expiresStr);
                    logger.info(lockKey+",oldValueStr="+oldValueStr);
                    // 获取上一个锁到期时间，并设置现在的锁到期时间，
                    // 只有一个线程才能获取上一个线上的设置时间，因为jedis.getSet是同步的
                    if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
                        logger.info("Get a successful distribution lock via jedis.getSet!  lock.lockKey="+lockKey);
                        // 如过这个时候，多个线程恰好都到了这里，但是只有一个线程的设置值和当前值相同，他才有权利获取锁
                        // lock acquired
                        locked = true;
                        return true;
                    }
                }
                timeout -= 100;
                Thread.sleep(100);
            }
            return false;
    	}
    }

    /**
     * Acqurired lock release.
     */
    public synchronized void release() {
    	try(Jedis jedis = getClient()){
    		if (locked) {
                logger.info("release begin release lock,lockKey=" + lockKey);
                jedis.del(lockKey);
                locked = false;
            }
    	}
    }

    public synchronized void release(String lockKey) {
    	try(Jedis jedis = getClient()){
    		if (locked) {
                logger.info("release1 begin release lock,lockKey=" + lockKey);
                jedis.del(lockKey);
                locked = false;
            }
    	}
    }
}
